package com.kim.oauth.common.handle;

import com.kim.common.utilcom.exception.BusinessCommonException;
import com.kim.oauth.common.constants.EnableDynamicAutEnum;
import com.kim.oauth.common.constants.Oauth2ExceptionEnum;
import com.kim.oauth.common.mapper.MenuMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 从数据库中获取url对应的role信息
 * <p>
 * <p>
 * 关于自定义这个类，我说如下几点：
 * 1.一开始注入了MenuService，MenuService的作用是用来查询数据库中url pattern和role的对应关系，
 * 查询结果是一个List集合，集合中是Menu类，Menu类有两个核心属性，一个是url pattern，
 * 即匹配规则(比如/admin/**)，还有一个是List<Role>,即这种规则的路径需要哪些角色才能访问。
 * 2.我们可以从getAttributes(Object o)方法的参数o中提取出当前的请求url，然后将这个请求url和数据库中查询出来的所有url pattern一一对照，
 * 看符合哪一个url pattern，然后就获取到该url pattern所对应的角色，当然这个角色可能有多个，
 * 所以遍历角色，最后利用SecurityConfig.createList方法来创建一个角色集合。
 * 3.第二步的操作中，涉及到一个优先级问题，比如我的地址是/employee/basic/hello,这个地址既能被/employee/**匹配，
 * 也能被/employee/basic/**匹配，这就要求我们从数据库查询的时候对数据进行排序，
 * 将/employee/basic/**类型的url pattern放在集合的前面去比较。
 * 4.如果getAttributes(Object o)方法返回null的话，意味着当前这个请求不需要任何角色就能访问，甚至不需要登录。
 * 但是在我的整个业务中，并不存在这样的请求，我这里的要求是，所有未匹配到的路径，都是认证(登录)后可访问，
 * 因此我在这里返回一个ROLE_LOGIN的角色，这种角色在我的角色数据库中并不存在，因此我将在下一步的角色比对过程中特殊处理这种角色。
 * 5.如果地址是/login_p，这个是登录页，不需要任何角色即可访问，直接返回null。
 * 6.getAttributes(Object o)方法返回的集合最终会来到AccessDecisionManager类中，接下来我们再来看AccessDecisionManager类。
 */
@Slf4j
@Component
@ConditionalOnExpression("${surge.oauth2.token-store.enable-dynamic-auth:true} || ${surge.oauth2.token-store.enable-dynamic-scope-auth:true}")
public class UrlRolesFilterHandler implements FilterInvocationSecurityMetadataSource {

    /**
     * 动态路由默认关闭
     */
    private boolean enableDynamicAuth = false;

    /**
     * scope默认关闭
     */
    private boolean enableDynamicScopeAuth = false;

    /**
     * Scope规则
     */
    private Map<String, List<String>> scopeRule;

    /**
     * 菜单(url)Mapper
     */
    private final MenuMapper menuMapper;

    public UrlRolesFilterHandler(MenuMapper menuMapper) {
        log.info("注入url动态权限");
        this.menuMapper = menuMapper;
    }

    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        // 动态权限开启状态
        ((FilterInvocation) object).getRequest().setAttribute(EnableDynamicAutEnum.ENABLE_DYNAMIC_AUTH.getName(), enableDynamicAuth);
        // 动态Scope开启状态
        ((FilterInvocation) object).getRequest().setAttribute(EnableDynamicAutEnum.ENABLE_DYNAMIC_SCOPE_AUTH.getName(), enableDynamicScopeAuth);
        // 请求URI
        String requestUrl = ((FilterInvocation) object).getRequestUrl();
        ArrayList<String> names = new ArrayList<>();
        // 开启动态路由
        log.info("是否开启动态路由 {} ,是否开启的动态scope {}", enableDynamicAuth, enableDynamicScopeAuth);
        if (enableDynamicAuth) {
            List<String> roleNames = menuMapper.selectRoleNamesByUrl(requestUrl);
            names.addAll(roleNames.stream().map(EnableDynamicAutEnum.ENABLE_DYNAMIC_AUTH.getPrefix()::concat).collect(Collectors.toList()));
        }
        // 开启动态scope
        if (enableDynamicScopeAuth) {
            List<String> scopeNames = new ArrayList<>();
            if (MapUtils.isEmpty(scopeRule)) {
                log.error("作用域规则为空");
                throw new BusinessCommonException(Oauth2ExceptionEnum.SCOPE_EMPTY.getCode(), Oauth2ExceptionEnum.SCOPE_EMPTY.getMsg());
            }
            // url 存在scope
            for (Map.Entry<String, List<String>> entry : scopeRule.entrySet()) {
                if (entry.getValue().contains(requestUrl)) {
                    scopeNames.add(entry.getKey());
                }
            }
            names.addAll(scopeNames.stream().map(EnableDynamicAutEnum.ENABLE_DYNAMIC_SCOPE_AUTH.getPrefix()::concat).collect(Collectors.toList()));
        }
        String[] toBeStored = new String[]{};
        String[] strings = names.toArray(toBeStored);
        if(strings.length == 0) {
            log.info("不需要进入投票器");
        }
        return SecurityConfig.createList(strings);
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return FilterInvocation.class.isAssignableFrom(clazz);
    }

    public boolean isEnableDynamicAuth() {
        return enableDynamicAuth;
    }

    public void setEnableDynamicAuth(boolean enableDynamicAuth) {
        this.enableDynamicAuth = enableDynamicAuth;
    }

    public boolean isEnableDynamicScopeAuth() {
        return enableDynamicScopeAuth;
    }

    public void setEnableDynamicScopeAuth(boolean enableDynamicScopeAuth) {
        this.enableDynamicScopeAuth = enableDynamicScopeAuth;
    }

    public Map<String, List<String>> getScopeRule() {
        return scopeRule;
    }

    public void setScopeRule(Map<String, List<String>> scopeRule) {
        this.scopeRule = scopeRule;
    }

}
